//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using C = config;
using Microsoft.Research.DataLayer;

namespace DataLayerBuilder
{
    public partial class DataGen
    {

        //Global list containing the security principals for the system.
        List<string> securityPrincipalList = new List<string>();
        
        void ProcessLayout(C.DataLayout config,
            out SystemDef sysInfo,
            out SystemPolicyDef sysPolicy,
            out GlobalSettingsDef globalSettings,            
            out List<ObjectDef> objects,
            out List<RelationDef> relations,
            out List<EnumDef> enumList)
        {            
            Dictionary<string, ObjectDef> objMap;
            Dictionary<string, EnumDef> enumValueList = new Dictionary<string, EnumDef>();
            Dictionary<string, RelationDef> relationList = new Dictionary<string, RelationDef>();
            List<C.Relation> selfRelations = new List<config.Relation>();
            sysInfo = new SystemDef(config);            
            objMap = ProcessDataObjects(config, ref enumValueList, ref relationList, ref selfRelations);
            ProcessBindableRelations(config, objMap, ref enumValueList, ref relationList);
            sysPolicy = ProcessSystemPolicy(config, objMap, ref enumValueList, ref relationList);
            globalSettings = ProcessGlobalSettings(config, objMap, ref enumValueList, ref relationList);            
            ProcessReferences(config, ref objMap);
            ProcessDataRelations(config, objMap, ref enumValueList, ref relationList);
            ProcessVersionRelations(config, objMap, ref enumValueList, ref relationList, ref selfRelations);
                        
            objects = new List<ObjectDef>(objMap.Values.ToArray<ObjectDef>());
            enumList = new List<EnumDef>(enumValueList.Values.ToArray<EnumDef>());
            relations = new List<RelationDef>(relationList.Values.ToArray<RelationDef>());
        }
        
        /// <summary>
        /// Process the Global Settings config node.
        /// </summary>
        /// <param name="config">DataLayout config.</param>
        /// <returns></returns>
        private GlobalSettingsDef ProcessGlobalSettings(C.DataLayout config, Dictionary<string, ObjectDef> objList,
            ref Dictionary<string, EnumDef> enumValueList, ref Dictionary<string, RelationDef> relationList)
        {   
            /// Create the globalSettingDef object.
            GlobalSettingsDef globalSettingDef = new GlobalSettingsDef();   
             
            if (config.GlobalSettings != null)
            {   
                // Loop through all the settings within the global settings node.
                foreach (C.Setting setting in config.GlobalSettings.Setting)
                {
                    //Create a ConfigItemDef object and add to the global settings object.
                    ConfigItemDef settingDef = new ConfigItemDef(setting.name, setting.type, setting.uiLocResource, setting.uiNolocResourceText);
                    globalSettingDef.settings.Add(settingDef);                    
                }             
            }

            CreateGlobalSettingsObj(globalSettingDef, config, objList, ref enumValueList, ref relationList);                
            

            return globalSettingDef;
        }

        private void CreateGlobalSettingsObj(GlobalSettingsDef globalSettings,
           C.DataLayout config,
           Dictionary<string, ObjectDef> objList,
           ref Dictionary<string, EnumDef> enumValueList, ref Dictionary<string, RelationDef> relationList)
        {            
            if (globalSettings != null)
            {
                //<Object name="SystemPolicy" parent="BasicObject">                
                C.Object globalSettingObj = new C.Object();
                globalSettingObj.name = "GlobalSetting";
                globalSettingObj.parent = C.objectParent.BasicObject;

                List<object> globalSettingList = new List<object>();

                foreach (ConfigItemDef setting in globalSettings.settings)
                {
                    C.Field settingField = new C.Field();
                    settingField.name = setting.Name;
                    settingField.type = setting.Type;
                    settingField.required = true;
                    globalSettingList.Add(settingField);
                }

                globalSettingObj.Items = globalSettingList.ToArray();

                ObjectDef GlobalSetting = new ObjectDef(globalSettingObj);
                GlobalSetting.AutoGenerated = true;
                GlobalSetting.IsBlobStore = false;
                objList.Add(globalSettingObj.name, GlobalSetting);
                ProcessSingleDataObject(config, globalSettingObj, GlobalSetting, ref objList, ref enumValueList, ref relationList);
            }
        }

        /// <summary>
        /// Processes the system policy.
        /// </summary>
        /// <param name="config">The config.</param>
        /// <param name="objList">The obj list.</param>
        /// <param name="enumValueList">The enum value list.</param>
        /// <param name="relationList">The relation list.</param>
        /// <returns></returns>
        private SystemPolicyDef ProcessSystemPolicy(C.DataLayout config, Dictionary<string, ObjectDef> objList,
            ref Dictionary<string, EnumDef> enumValueList, ref Dictionary<string, RelationDef> relationList)
        {
            // Create the sysPolicyDef object.
            SystemPolicyDef sysPolicyDef = new SystemPolicyDef();    
            
            if (config.SystemPolicy != null)
            {                
                // List of role types.
                List<string> roleTypes = new List<string>();

                // List of builtin role types.
                List<string> builtinRoleTypes = new List<string>();

                // Custom roles will have a builtinRoleType of "None".
                builtinRoleTypes.Add("None");                
                
                foreach (object item in config.SystemPolicy.Items)
                {
                    if (item is C.Policy)
                    {
                        C.Policy policy = (C.Policy)item;
                        ConfigItemDef policyDef = new ConfigItemDef(policy.name, policy.type, policy.uiLocResource, policy.uiNolocResourceText);
                        sysPolicyDef.policys.Add(policyDef);
                    }
                    else if (item is C.Role)
                    {
                        C.Role role = (C.Role)item;                        
                        roleTypes.Add(role.name);                        
                        
                        if (role.type == C.roletype.builtinAdmins && !builtinRoleTypes.Contains("Administrators"))
                        {
                            builtinRoleTypes.Add("Administrators");
                        }
                        else if (role.type == C.roletype.builtinUsers && !builtinRoleTypes.Contains("Users"))
                        {
                            builtinRoleTypes.Add("Users");
                        }
                        else if (role.type == C.roletype.builtinServices && !builtinRoleTypes.Contains("Services"))
                        {
                            builtinRoleTypes.Add("Services");
                        }
                        else if (role.type == C.roletype.builtinGuests && !builtinRoleTypes.Contains("Guests"))
                        {
                            builtinRoleTypes.Add("Guests");
                        }

                        RoleDef roledef = new RoleDef(role);
                        sysPolicyDef.roles.Add(roledef);
                    }
                }
                // This will create the SystemRole object that holds all the roles available in the system.
                CreateSystemRoleObj(builtinRoleTypes, roleTypes, config, objList, ref enumValueList, ref relationList);
                CreateSystemPolicyObj(sysPolicyDef, config, objList, ref enumValueList, ref relationList);                
                sysPolicyDef.builtinRoles = builtinRoleTypes;                               
            }
            return sysPolicyDef;
        }

        private void CreateSystemPolicyObj(SystemPolicyDef systemPolicy,
            C.DataLayout config,
            Dictionary<string, ObjectDef> objList,
            ref Dictionary<string, EnumDef> enumValueList, ref Dictionary<string, RelationDef> relationList)
        {
            C.Object policyObj = new C.Object();
            policyObj.name = "SystemPolicy";
            policyObj.parent = C.objectParent.BasicObject;

            List<object> policyObjList = new List<object>();

            foreach (ConfigItemDef policy in systemPolicy.policys)
            {                
                C.Field policyField = new C.Field();
                policyField.name = policy.Name;
                policyField.type = policy.Type;
                policyField.required = true;
                policyObjList.Add(policyField);
            }

            policyObj.Items = policyObjList.ToArray();

            ObjectDef SystemPolicy = new ObjectDef(policyObj);
            SystemPolicy.AutoGenerated = true;
            SystemPolicy.IsBlobStore = false;
            objList.Add(policyObj.name, SystemPolicy);
            ProcessSingleDataObject(config, policyObj, SystemPolicy, ref objList, ref enumValueList, ref relationList);


        }


        /// <summary>
        /// Creates the system role obj.
        /// </summary>
        /// <param name="builtinRoleTypes">The builtin role types.</param>
        /// <param name="roleTypes">The role types.</param>
        /// <param name="config">The config.</param>
        /// <param name="objList">The obj list.</param>
        /// <param name="enumValueList">The enum value list.</param>
        /// <param name="relationList">The relation list.</param>
        private void CreateSystemRoleObj(List<string> builtinRoleTypes, List<string> roleTypes,
            C.DataLayout config,
            Dictionary<string, ObjectDef> objList,
            ref Dictionary<string, EnumDef> enumValueList, ref Dictionary<string, RelationDef> relationList)
        {
            List<object> roleObjList = new List<object>();
            
            //<Object name="SystemRole" parent="BasicObject">
            C.Object roleObj = new C.Object();
            roleObj.name = "SystemRole";
            roleObj.parent = C.objectParent.BasicObject;

            //<Enum name="Role" enum="SystemRoleType">                               
            C.Enum roleEnum = new C.Enum();
            roleEnum.name = "Role";
            roleEnum.@enum = "SystemRoleType";
            roleEnum.required = true;
            roleEnum.Value = roleTypes.ToArray();

            roleObjList.Add(roleEnum);

            //<Enum name="BuiltinRole" enum="SystemBuiltinRoleType">
            C.Enum builtinRoleEnum = new C.Enum();
            builtinRoleEnum.name = "BuiltinRole";
            builtinRoleEnum.@enum = "SystemBuiltinRoleType";
            builtinRoleEnum.required = true;
            builtinRoleEnum.Value = builtinRoleTypes.ToArray();

            roleObjList.Add(builtinRoleEnum);

            //<SecurityPrincipalRef name="Member"/>
            C.SecurityPrincipalRef secPrinRef = new config.SecurityPrincipalRef();
            secPrinRef.name = "Member";
            secPrinRef.required = true;
            secPrinRef.twoWay = true;
            secPrinRef.twoWaySpecified = true;
            secPrinRef.revVisibility = C.visibility.@public;
            secPrinRef.revVisibilitySpecified = true;
            roleObjList.Add(secPrinRef);
          
            roleObj.Items = roleObjList.ToArray();

            ObjectDef SystemRole = new ObjectDef(roleObj);
            SystemRole.AutoGenerated = true;
            SystemRole.IsBlobStore = false;
            objList.Add(roleObj.name, SystemRole);
            ProcessSingleDataObject(config, roleObj, SystemRole, ref objList, ref enumValueList, ref relationList);

        }


        Dictionary<string, ObjectDef> ProcessDataObjects(C.DataLayout config, ref Dictionary<string, EnumDef> enumValueList,
            ref Dictionary<string, RelationDef> relationList, ref List<C.Relation> selfRelations)
        {
            Dictionary<string, ObjectDef> objectList = new Dictionary<string, ObjectDef>();

            foreach (C.Object obj in config.Objects)
            {
                Log(0, "Processing '{0}'...", obj.name);

                ObjectDef objDef = new ObjectDef(obj);
                objectList.Add(obj.name, objDef);               

                // now process the object
                ProcessSingleDataObject(config, obj, objDef, ref objectList, ref enumValueList, ref relationList);

                // we need to have self relationships to handle object version relationships. Addind them here.
                AddVersionRelationships(selfRelations, objDef);
            }

            return objectList;
        }

        private void ProcessBindableRelations(config.DataLayout config,
            Dictionary<string, ObjectDef> objectList,
            ref Dictionary<string, EnumDef> enumValueList,
            ref Dictionary<string, RelationDef> relationList)
        {
            foreach (C.Object obj in config.Objects)
            {
                // now let's add the code related to binding these objects to external resources.
                if (obj.bindable && objectList.ContainsKey(obj.name))
                {
                    // this is where we hold binding information for other data sources. 
                    AddBindings(config, objectList[obj.name], objectList, ref enumValueList, ref relationList);
                }
            }
        }

        private void AddBindings(config.DataLayout config, ObjectDef objDef, Dictionary<string, ObjectDef> objList, ref Dictionary<string, EnumDef> enumValueList, ref Dictionary<string, RelationDef> relationList)
        {
            // having an enub definition means that we have some fields to bind to external repos
            Log(1, "Adding binding relation to {0}", objDef.Name);

            //C.Relation relationObject = new C.Relation();

            //// Actual object.
            //relationObject.one = objDef.Name;

            //// DataStore object.
            //relationObject.two = "DataReferenceStore";

            //relationObject.twoWay = true;

            //relationObject.visibility = C.visibility.@internal;

            //relationObject.@object = string.Format("{0}Binding", objDef.Name);

            //RelationDef relInfo = new RelationDef(relationObject);
            //relationList.Add(relInfo.TableName, relInfo);

            //if (objList.ContainsKey(relationObject.one))
            //    objList[relationObject.one].Relations.Add(relInfo);

            //if (relationObject.twoWay)
            //    if (objList.ContainsKey(relationObject.two))
            //        if (!objList[relationObject.two].Relations.Contains(relInfo))
            //            objList[relationObject.two].Relations.Add(relInfo);

            //// Fields
            //// this will hold the unique identifier of the object craeted. 
            //// this value will be used to get data from the external data registry.
            //relInfo.Fields.Add(new FieldDef("Key", C.datatype.@string, null, false));

            //// this will denote the priority of the binding. The same field might have different bindings. This position will
            //// denote the priority and will be available to be changed in the runtime
            //relInfo.Fields.Add(new FieldDef("Position", C.datatype.integer, null, false));

            //// add the field enum. We are referecing the enum created within the object class here
            //C.Enum myEnum = new C.Enum();
            //myEnum.name = "Field";
            //myEnum.@enum = string.Format("{0}.{1}.{2}", objDef.Name, "IBindable", "Fields");
            //myEnum.required = true;
            //relInfo.Enums.Add(ProcessEnum(myEnum, ref enumValueList));


            C.Object bindingsObj = new C.Object();
            bindingsObj.name = string.Format("{0}Binding", objDef.Name);
            bindingsObj.parent = C.objectParent.BasicObject;
            bindingsObj.visibility = C.visibility.@internal;

            ObjectDef bindingObjDef = new ObjectDef(bindingsObj);
            objList.Add(bindingsObj.name, bindingObjDef);

            // parameters to the data provider. these parameters will be understood by the provider and will contain
            // enough information to get the data from the data source. For example, in case of a file provider,
            // this parameter will hold the folder name and the file name, encoded in some way (I used URL encoding with file provider)
            bindingObjDef.Fields.Add(new FieldDef("Key", C.datatype.@string, null, false));

            // this will denote the priority of the binding. The same field might have different bindings. This position will
            // denote the priority and will be available to be changed in the runtime
            bindingObjDef.Fields.Add(new FieldDef("Position", C.datatype.integer, null, false));

            // add the field enum. We are referecing the enum created within the object class here
            C.Enum myEnum = new C.Enum();
            myEnum.name = "Field";
            myEnum.@enum = string.Format("{0}.{1}.{2}", objDef.Name, "IBindable", "Fields");
            myEnum.required = true;
            bindingObjDef.Enums.Add(ProcessEnum(myEnum, ref enumValueList));

            // Add a reference to the parent object.
            C.Reference parentReference = new C.Reference();
            parentReference.name = objDef.Name;
            parentReference.@object = objDef.Name;
            parentReference.twoWay = true;
            parentReference.required = true;

            // Add a reference to the DataReferenceStore object.
            C.Reference dataStoreReference = new C.Reference();
            dataStoreReference.name = "DataReferenceStore";
            dataStoreReference.@object = "DataReferenceStore";
            dataStoreReference.twoWay = true;
            dataStoreReference.required = true;

            // add the Reference
            bindingObjDef.References.Add(new RefDef(bindingObjDef, parentReference));
            bindingObjDef.References.Add(new RefDef(bindingObjDef, dataStoreReference));
        }

        private void AddVersionRelationships(List<C.Relation> selfRelations, ObjectDef objDef)
        {
            if (objDef.Versioned)
            {
                // Check for reserved field name collisions
                CheckForReservedVersionFieldCollisions(objDef);

                // <Reference name="SafeParentVersion" object="Activity" />
                C.Reference safeRef = new config.Reference();
                safeRef.name = "SafeParentVersion";
                safeRef.@object = objDef.Name;
                safeRef.twoWay = true;
                safeRef.twoWaySpecified = true;
                safeRef.revName = "SafeChildVersion";
                safeRef.visibility = config.visibility.@protected;
                safeRef.visibilitySpecified = true;
                safeRef.revVisibility = config.visibility.@protected;
                safeRef.revVisibilitySpecified = true;
                RefDef safeRefDef = new RefDef(objDef, safeRef);
                objDef.References.Add(safeRefDef);

                // <Field name="Version" type="string" />
                FieldDef verFieldDef = new FieldDef("Version", config.datatype.integer, null, false);
                objDef.Fields.Add(verFieldDef);

                string name = objDef.Name;
                C.Relation selfRelation = new C.Relation();
                selfRelation.@object = name + "Version";

                // <Relation object="ActivityVersion" one="Activity" two="Activity"
                //      oneName="NewerVersion" twoName="OlderVersion" twoWay="true">
                selfRelation.one = name;
                selfRelation.two = name;
                selfRelation.oneName = "NewerVersion";
                selfRelation.twoName = "OlderVersion";
                selfRelation.twoWay = true;
                selfRelation.twoWaySpecified = true;

                //  <Enum name="Type" enum="ActivityVersionType" required="true">
                //    <Value>Concrete</Value>
                //    <Value>Contribution</Value>
                //  </Enum>

                C.Enum myEnum = new C.Enum();
                myEnum.name = "Type";
                myEnum.@enum = "VersionRelationshipType";
                myEnum.required = true;
                selfRelation.Items = new object[] { myEnum };
                selfRelations.Add(selfRelation);
            }
        }

        void CheckForReservedVersionFieldCollisions(ObjectDef obj)
        {
            string[] reservedNames = { 
                 "version", 
                 "safechildversion", 
                 "safeparentversion", 
             };

            foreach (FieldDef f in obj.Fields)
            {
                if ((f.Name != null && reservedNames.Contains<string>(f.Name.ToLowerInvariant())))
                    throw new ApplicationException(string.Format("Object {0} defined the reserved field {1}", obj.Name, f.Name));
            }

            foreach (EnumDef e in obj.Enums)
            {
                if ((e.Name != null && reservedNames.Contains<string>(e.Name.ToLowerInvariant())))
                    throw new ApplicationException(string.Format("Object {0} defined the reserved field {1}", obj.Name, e.Name));
            }

            foreach (RefDef r in obj.References)
            {
                if (r.Name != null && reservedNames.Contains<string>(r.Name.ToLowerInvariant()))
                    throw new ApplicationException(string.Format("Object {0} defined the reserved field {1}", obj.Name, r.Name));
                if (r.RevName != null && reservedNames.Contains<string>(r.RevName.ToLowerInvariant()))
                    throw new ApplicationException(string.Format("Object {0} defined the reserved field {1}", obj.Name, r.RevName));
            }

            foreach (RelationDef r in obj.Relations)
            {
                if (r.One != null && reservedNames.Contains<string>(r.One.ToLowerInvariant()))
                    throw new ApplicationException(string.Format("Object {0} defined the reserved field {1}", obj.Name, r.One));
                if (r.Two != null && reservedNames.Contains<string>(r.Two.ToLowerInvariant()))
                    throw new ApplicationException(string.Format("Object {0} defined the reserved field {1}", obj.Name, r.Two));
                if (r.OneName != null && reservedNames.Contains<string>(r.OneName.ToLowerInvariant()))
                    throw new ApplicationException(string.Format("Object {0} defined the reserved field {1}", obj.Name, r.OneName));
                if (r.TwoName != null && reservedNames.Contains<string>(r.TwoName.ToLowerInvariant()))
                    throw new ApplicationException(string.Format("Object {0} defined the reserved field {1}", obj.Name, r.TwoName));
            }

            foreach (RefDef r in obj.RevRef)
            {
                if (r.Name != null && reservedNames.Contains<string>(r.Name.ToLowerInvariant()))
                    throw new ApplicationException(string.Format("Object {0} defined the reserved field {1}", obj.Name, r.Name));
                if (r.RevName != null && reservedNames.Contains<string>(r.RevName.ToLowerInvariant()))
                    throw new ApplicationException(string.Format("Object {0} defined the reserved field {1}", obj.Name, r.RevName));
            }
        }

        void ProcessSingleDataObject(C.DataLayout config, C.Object obj, ObjectDef objDef,
            ref Dictionary<string, ObjectDef> objectList, ref Dictionary<string, EnumDef> enumValueList,
            ref Dictionary<string, RelationDef> relationList)
        {          
            
            if (obj.Items != null)
            {
                    if (obj.SecurityPrincipal != null)
                    {
                        Log(1, "SecurityPrincipal: {0} ", obj.name);
                        // <Implements interface="ISecurityPrincipal" />
                        C.Implements prinImpl = new C.Implements();
                        prinImpl.@interface = C.@interface.ISecurityPrincipal;
                        objDef.Implements.Add(new ImplDef(prinImpl));
                        
                        #region Creating the ObjectCredential Object.
                        
                        //<Object name="ObjectCredential" parent="BasicObject">
                        string credentialName = string.Format("{0}{1}", obj.name, "Credential");
                        C.Object credentialObj = new C.Object();
                        credentialObj.name = credentialName;
                        credentialObj.parent = C.objectParent.BasicObject;
                        
                        // <Implements interface="ISecurityCredential" />
                        C.Implements credImpl = new C.Implements();
                        credImpl.@interface = C.@interface.ISecurityCredential;

                        // <CollImplements interface="ISecurityCredentialCollection" />
                        C.CollImplements credCollimpl = new C.CollImplements();
                        credCollimpl.@interface = C.@interface.ISecurityCredentialCollection;

                        //<Reference name="Object" object="Object" required="true" twoWay="true" />
                        C.Reference prinRef = new config.Reference();
                        prinRef.name = obj.name;
                        prinRef.@object = obj.name;
                        prinRef.required = true;
                        prinRef.requiredSpecified = true;
                        prinRef.twoWay = true;
                        prinRef.twoWaySpecified = true;
                        prinRef.revName = string.Format("{0}{1}", obj.name, "Credential");
                        prinRef.revVisibility = C.visibility.@protected;
                        prinRef.revVisibilitySpecified = true;

                        //<Enum name="Type" enum="CredentialType">
                          // <Value>Basic</Value>
                          // <Value>Windows</Value>
                          // <Value>Certificate</Value>
                          // <Value>Other</Value>
                        //</Enum>
                        C.Enum credEnum = new C.Enum();
                        credEnum.name = "Type";
                        credEnum.@enum = "CredentialType";
                        credEnum.required = true;
                        //Fetching the values from the data layer common enum.
                        credEnum.Value = Enum.GetNames(typeof(CredentialType));

                        //<Field name="Credential" type="string" />
                        C.Field credentialStr = new config.Field();
                        credentialStr.name = "Credential";
                        credentialStr.type = C.datatype.@string;
                        credentialStr.required = true;

                        //<Field name="DisplayName" type="string" />
                        C.Field displayNameStr = new config.Field();
                        displayNameStr.name = "DisplayName";
                        displayNameStr.type = C.datatype.@string;
                        displayNameStr.required = false;

                        credentialObj.Items = new object[] { credImpl, credCollimpl, prinRef, credEnum, credentialStr, displayNameStr };

                        // Generate artificial relationship with parent object
                        ObjectDef credential = new ObjectDef(credentialObj);
                        credential.AutoGenerated = true;
                        credential.IsBlobStore = false;
                        
                        objectList.Add(credentialObj.name, credential);
                        securityPrincipalList.Add(obj.name);
                        ProcessSingleDataObject(config, credentialObj, credential, ref objectList, ref enumValueList, ref relationList);

                        #endregion

                    }
                    if (obj.Securable != null)
                    {
                        Log(1, "Securable: {0} ", obj.name);
                        
                        # region ObjectACL creation

                        //<Object name="ObjectACL" parent="BasicObject">
                        string aclName = string.Format("{0}{1}", obj.name, "ACL");
                        C.Object aclObj = new C.Object();
                        aclObj.name = aclName;
                        aclObj.parent = C.objectParent.BasicObject;

                        //<Reference name="Object" object="Object" required="true" twoWay="true" />
                        C.Reference objRef = new config.Reference();
                        objRef.name = obj.name;
                        objRef.@object = obj.name;
                        objRef.required = true;
                        objRef.requiredSpecified = true;
                        objRef.twoWay = true;
                        objRef.twoWaySpecified = true;
                        objRef.revVisibility = C.visibility.@protected;
                        objRef.revVisibilitySpecified = true;                        
                        
                        List<object> objList = new List<object>();
                        C.Securable sec = obj.Securable;
                        List<string> permList = new List<string>();
                        Dictionary<string, List<string>> privPermMapping = new Dictionary<string, List<string>>();
                        
                        // Looping through all the permissions to get the permission and privilege enums.
                        // Also generating the privilege perm dictionary that will be used for the add/remove 
                        // access logic.
                        for (int i = 0; i < sec.Permission.Length; i++)
                        {
                            permList.Add(sec.Permission[i].name);                            
                            C.Enum privEnum = new C.Enum();
                            privEnum.name = string.Format("{0}{1}", sec.Permission[i].name, "Priv");
                            privEnum.@enum = string.Format("{0}{1}{2}", obj.name, sec.Permission[i].name, "Priv");
                            
                            List<string> enumValues = new List<string>();
                            if (sec.Permission[i].type == C.permissiontype.allowDenyRight)
                            {
                                enumValues.Add("None");
                                enumValues.Add("Allow");
                                if (!privPermMapping.ContainsKey("Allow"))
                                {
                                    List<string> pList = new List<string>();
                                    pList.Add(sec.Permission[i].name);
                                    privPermMapping.Add("Allow", pList);
                                }
                                else
                                {
                                    privPermMapping["Allow"].Add(sec.Permission[i].name);
                                }
                                enumValues.Add("Deny");
                                if (!privPermMapping.ContainsKey("Deny"))
                                {
                                    List<string> pList = new List<string>();
                                    pList.Add(sec.Permission[i].name);
                                    privPermMapping.Add("Deny", pList);
                                }
                                else
                                {
                                    privPermMapping["Deny"].Add(sec.Permission[i].name);
                                }
                            }
                            else
                            {
                                enumValues.Add("None");
                                for (int j = 0; j < sec.Permission[i].Right.Length; j++)
                                {
                                    enumValues.Add(sec.Permission[i].Right[j].ToString());
                                    if (!privPermMapping.ContainsKey(sec.Permission[i].Right[j].ToString()))
                                    {
                                        List<string> pList = new List<string>();
                                        pList.Add(sec.Permission[i].name);
                                        privPermMapping.Add(sec.Permission[i].Right[j].ToString(), pList);
                                    }
                                    else
                                    {
                                        privPermMapping[sec.Permission[i].Right[j].ToString()].Add(sec.Permission[i].name);
                                    }
                                }
                            }
                            
                            privEnum.Value = enumValues.ToArray();
                            objList.Add(privEnum);   
                        }

                        //<Enum name="Permission" enum="ActivityPermissionType" required="true">
                        //  <Value>RunWorkflow</Value>
                        //  <Value>AuthorWorkflow</Value>
                        //</Enum>                          
                        C.Enum permEnum = new C.Enum();
                        permEnum.name = "Permission";
                        permEnum.@enum = "ActivityPermissionType";                        
                        permEnum.Value = permList.ToArray();

                        objList.Add(objRef);
                        objList.Add(permEnum);

                        //<SecurityPrincipalRef name="PrivOwner"/>
                        C.SecurityPrincipalRef secPrinRef = new config.SecurityPrincipalRef();
                        secPrinRef.name = "PrivOwner";
                        secPrinRef.required = true;
                        secPrinRef.requiredSpecified = true;
                        objList.Add(secPrinRef);                        

                        SecPrivilegeDef secPrivDef = new SecPrivilegeDef(objDef, privPermMapping); 
                        aclObj.Items = objList.ToArray();

                        // Generate artificial relationship with parent object
                        ObjectDef acl = new ObjectDef(aclObj, secPrivDef);
                        acl.AutoGenerated = true;
                        acl.IsBlobStore = false;
                        objectList.Add(aclObj.name, acl);
                        ProcessSingleDataObject(config, aclObj, acl, ref objectList, ref enumValueList, ref relationList);
                        # endregion
                    }

                foreach (object item in obj.Items)
                {

                    if (item is C.Field)
                    {
                        // Fields
                        C.Field field = (C.Field)item;
                        Log(1, "Field: {0} [{1}]", field.name, field.type.ToString());

                        if (field.name == "Name" &&
                            objDef.BaseClass == ParentClasses.NamedObject.ToString())
                        {
                            throw new ApplicationException("NamedObject already has name");
                        }

                        FieldDef fieldObj = new FieldDef(field.name, field.type, field.storeAs, field.required);
                        objDef.Fields.Add(fieldObj);

                        if (fieldObj.IsBLOB)
                        {
                            //
                            // Generate a field for cache of blob size
                            //
                            string blobSizeName = string.Format("{0}Size", field.name);
                            FieldDef blobSizeObj = new FieldDef(blobSizeName, C.datatype.integer, blobSizeName, false);
                            objDef.Fields.Add(blobSizeObj);

                            //
                            // Generate relationship to store the blobs
                            //

                            #region Blob C.Object creation
                            // <Object name="WFCategoryIconBlob" parent="BasicObject">
                            string blobName = string.Format("{0}{1}Blob", obj.name, field.name);
                            C.Object relBlob = new config.Object();
                            relBlob.name = blobName;
                            relBlob.parent = C.objectParent.BasicObject;

                            // <Implements interface="IBlob" />
                            C.Implements impl1 = new config.Implements();
                            impl1.@interface = C.@interface.IBlob;

                            // <Reference name="WFCategory" object="WFCategory" required="true" twoWay="true" revName="IconStore" />
                            C.Reference ref1 = new config.Reference();
                            ref1.name = obj.name;
                            ref1.@object = obj.name;
                            ref1.required = true;
                            ref1.requiredSpecified = true;
                            ref1.twoWay = true;
                            ref1.twoWaySpecified = true;
                            ref1.revName = string.Format("{0}Store", field.name);
                            ref1.revVisibility = C.visibility.@protected;
                            ref1.revVisibilitySpecified = true;

                            // <Field name="PackageID" type="guid" required="true" />
                            C.Field f1 = new config.Field();
                            f1.name = "PackageID";
                            f1.type = C.datatype.guid;
                            f1.required = true;

                            // <Field name="Index" type="integer" required="true" />
                            C.Field f2 = new config.Field();
                            f2.name = "Index";
                            f2.type = C.datatype.integer;
                            f2.required = true;

                            // <Field name="Chunks" type="integer" required="true" />
                            C.Field f3 = new config.Field();
                            f3.name = "Chunks";
                            f3.type = C.datatype.integer;
                            f3.required = true;

                            // <Field name="Contents" type="string" />
                            C.Field f4 = new config.Field();
                            f4.name = "Contents";
                            f4.type = C.datatype.@string;
                            f4.required = false;

                            // <Reference name="Previous" object="WFCategoryIconBlob" />
                            C.Reference ref2 = new config.Reference();
                            ref2.name = "Previous";
                            ref2.@object = blobName;

                            // <Reference name="Next" object="WFCategoryIconBlob" />
                            C.Reference ref3 = new config.Reference();
                            ref3.name = "Next";
                            ref3.@object = blobName;

                            // <Field name="RevisionID" type="guid" required="true" />
                            C.Field f5 = new config.Field();
                            f5.name = "RevisionID";
                            f5.type = C.datatype.guid;
                            f5.required = true;

                            relBlob.Items = new object[] { impl1, ref1, f1, f2, f3, f4, ref2, ref3, f5 };
                            #endregion

                            // Generate artificial relationship with parent object
                            ObjectDef relObject = new ObjectDef(relBlob);
                            relObject.AutoGenerated = true;
                            relObject.IsBlobStore = true;
                            objectList.Add(relBlob.name, relObject);                            
                            ProcessSingleDataObject(config, relBlob, relObject, ref objectList, ref enumValueList, ref relationList);
                        }
                    }
                    else if (item is C.Enum)
                    {
                        // Enums
                        C.Enum myEnum = (C.Enum)item;
                        Log(1, "Enum: {0}", myEnum.name, myEnum.@enum);

                        if (myEnum.name == "Name" &&
                            objDef.BaseClass == ParentClasses.NamedObject.ToString())
                        {
                            throw new ApplicationException("NamedObject already has name");
                        }

                        EnumDef enumObj = ProcessEnum(myEnum, ref enumValueList);
                        objDef.Enums.Add(enumObj);
                    }
                    else if (item is C.Reference)
                    {
                        // References
                        C.Reference refs = (C.Reference)item;
                        Log(1, "Reference: {0} -> {1}", refs.name, refs.@object);

                        if (refs.name == "Name" &&
                            objDef.BaseClass == ParentClasses.NamedObject.ToString())
                        {
                            throw new ApplicationException("NamedObject already has name");
                        }

                        objDef.References.Add(new RefDef(objDef, refs));
                    }

                    else if (item is C.Implements)
                    {
                        // Implements
                        C.Implements impl = (C.Implements)item;
                        Log(1, "Implements: {0}", impl.@interface.ToString());
                        objDef.Implements.Add(new ImplDef(impl));

                    }

                    else if (item is C.CollImplements)
                    {
                        // CollImplements
                        C.CollImplements impl = (C.CollImplements)item;
                        Log(1, "CollImplements: {0}", impl.@interface.ToString());
                        objDef.CollImplements.Add(new CollImplDef(impl));

                    }                                        
              
                    else if (item is C.SecurityPrincipalRef)
                    {
                        C.SecurityPrincipalRef secPrinRef = (C.SecurityPrincipalRef)item;
                        Log(1, "SecurityPrincipalRef: {0}", secPrinRef.name);
                        objDef.SecPrinRef.Add(new SecPrincipalRefDef(objDef, secPrinRef));
                        foreach (string securityPrincipal in securityPrincipalList)
                        {
                            String fieldName = string.Format("{0}{1}{2}", secPrinRef.name, securityPrincipal, "Principal");
                            C.Reference secRef = new config.Reference();
                            secRef.name = string.Format(fieldName);
                            secRef.@object = securityPrincipal;
                            secRef.required = false;
                            secRef.twoWay = secPrinRef.twoWay;
                            secRef.twoWaySpecified = secPrinRef.twoWaySpecified;
                            secRef.revVisibility = secPrinRef.revVisibility;
                            secRef.revVisibilitySpecified = secPrinRef.revVisibilitySpecified;
                            objDef.References.Add(new RefDef(objDef, secRef));
                            
                        }                     
                    }
                }
            }
        }



        EnumDef ProcessEnum(C.Enum myEnum, ref Dictionary<string, EnumDef> enumValueList)
        {
            EnumDef enumObj;

            enumObj = new EnumDef(myEnum.name, myEnum.@enum, myEnum.storeAs, myEnum.required);

            if (!enumValueList.ContainsKey(myEnum.@enum))
            {
                enumValueList.Add(myEnum.@enum, enumObj);
            }

            if (myEnum.Value != null && myEnum.Value.Length > 0)
            {
                // Values for the enum
                enumObj.Values.AddRange(myEnum.Value);
            }

            return enumObj;
        }

        void ProcessReferences(C.DataLayout config, ref Dictionary<string, ObjectDef> objList)
        {
            foreach (ObjectDef obj in objList.Values)
            {
                // Check references
                if (obj.References.Count > 0)
                {
                    foreach (RefDef refInfo in obj.References)
                    {
                        // ... and cross-reference them
                        if (refInfo.IsTwoWay && objList.ContainsKey(refInfo.Type))
                        {
                            ObjectDef otherSide = objList[refInfo.Type];
                            otherSide.RevRef.Add(refInfo);
                        }
                        else if (objList.ContainsKey(refInfo.Type))
                        {
                            // OK reference
                        }
                        else
                        {
                            Console.WriteLine("Invalid <Reference> type {0}", refInfo.Type);
                        }
                    }
                }
            }
        }

        void ProcessDataRelations(C.DataLayout config, Dictionary<string, ObjectDef> objList,
            ref Dictionary<string, EnumDef> enumValueList, ref Dictionary<string, RelationDef> relationList)
        {
            if (config.Relations != null)
            {
                foreach (C.Relation rel in config.Relations)
                {
                    ProcessSingleDataRelation(config, rel, ref objList, ref enumValueList, ref relationList);
                }
            }
        }

        void ProcessVersionRelations(config.DataLayout config, Dictionary<string, ObjectDef> objList,
            ref Dictionary<string, EnumDef> enumValueList, ref Dictionary<string, RelationDef> relationList,
            ref List<C.Relation> selfRelations)
        {
            if (selfRelations != null)
            {
                foreach (C.Relation rel in selfRelations)
                {
                    ProcessSingleDataRelation(config, rel, ref objList, ref enumValueList, ref relationList);
                }
            }
        }

        void ProcessSingleDataRelation(C.DataLayout config,
            C.Relation rel,
            ref Dictionary<string, ObjectDef> objList,
            ref Dictionary<string, EnumDef> enumValueList,
            ref Dictionary<string, RelationDef> relationList)
        {
            Log(0, "Processing '{0}' <--> '{1}'...", rel.one, rel.two);

            RelationDef relInfo = new RelationDef(rel);
            relationList.Add(relInfo.TableName, relInfo);

            if (objList.ContainsKey(rel.one))
                objList[rel.one].Relations.Add(relInfo);

            if (rel.twoWay)
                if (objList.ContainsKey(rel.two))
                    if (!objList[rel.two].Relations.Contains(relInfo))
                        objList[rel.two].Relations.Add(relInfo);

            if (rel.Items != null)
            {
                foreach (object item in rel.Items)
                {
                    if (item is C.Field)
                    {
                        C.Field field = item as C.Field;
                        Log(1, "Field: {0} [{1}]", field.name, field.type.ToString());
                        relInfo.Fields.Add(new FieldDef(field.name, field.type, field.storeAs, false));
                    }
                    else if (item is C.Enum)
                    {
                        C.Enum myEnum = (C.Enum)item;
                        Log(1, "Enum: {0}", myEnum.name, myEnum.@enum);

                        EnumDef enumObj = ProcessEnum(myEnum, ref enumValueList);
                        relInfo.Enums.Add(enumObj);
                    }
                    else if (item is C.Reference)
                    {
                        throw new NotImplementedException("Not implemented yet");
                    }
                }
            }
        }
    }
}
